//    Copyright (C) Kherty.  All rights reserved.
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using OpenLS.Spreadsheet.UI.Internals;

namespace OpenLS.Spreadsheet.UI
{
    internal static class GridKeyboardNavigation
    {
        private static void moveActive(object sender, BlockNavigationMode navigationMode,
                                       NavigationDirection navigationDirection)
        {
            var ds = (DependencyObject) sender;

            EditionMode editionMode = EditionElement.GetMode(ds);
            if (editionMode == EditionMode.Edit)
            {
                return;
            }
            GridEditing.StopEditing(ds, false);
            Worksheet ws = getWorksheet(sender);
            bool isInEndMode = EditionElement.GetIsInEndMode(ds);
            if (isInEndMode)
                navigationMode = BlockNavigationMode.ByBlock;
            EditionElement.SetIsEndModeFor(ds, false);
            Contract.Assert(!EditionElement.GetIsInEndMode(ds));
            moveActive(sender, ws, navigationDirection, navigationMode);
            WorksheetPane pane = SpreadsheetElement.GetWorksheetPane(ds);
            var activeCell = SpreadsheetElement.GetWorkingActiveCell(sender);
            if (sender is WorksheetGrid && activeCell != null && pane != null)
            {
                pane = pane.Worksheet.GetPaneFor(activeCell);
                pane.EnsureVisible(activeCell);
            }
        }

        private static void moveActive(object sender, Worksheet ws, NavigationDirection navigationDirection, BlockNavigationMode mode)
        {
            Cell c = SpreadsheetElement.GetWorkingActiveCell(sender);
            if (c == null)
            {
                c = ws[0, 0];
                SpreadsheetElement.SetCurrentRangeForRoot(sender, c.ToRange(), c);
               // ws[0, 0].Select();
                Debug.WriteLine("c == null");
                return;
            }
            var c2 = RangeNavigator.GetVisibleNeighbor(c, navigationDirection, mode);
            if (c2 == null)
                return;
            SpreadsheetElement.SetCurrentRangeForRoot(sender, c2.ToRange(), c2);
               
        }

        private static Worksheet getWorksheet(object sender)
        {
            return WorksheetHelper.GetWorksheet(sender);
        }

        private static void onCanMoveDown(object sender, CanExecuteRoutedEventArgs args)
        {
            args.CanExecute = true;
        }

        private static void onCanMoveUp(object sender, CanExecuteRoutedEventArgs args)
        {
            args.CanExecute = true;
        }

        private static void onAlways(object sender, CanExecuteRoutedEventArgs args)
        {
            args.CanExecute = true;
        }

        private static void select(object sender, BlockNavigationMode blockNavigationMode,
                                   NavigationDirection navigationDirection)
        {
            Worksheet ws = getWorksheet(sender);
            Cell oldcell = SpreadsheetElement.GetWorkingActiveCell(sender);
            var oldSelection = SpreadsheetElement.GetWorkingSelection(sender);
            if (oldSelection == null)
                return;
            CellAddress a = oldcell.Address;
            Debug.WriteLine("with keyboard, a = " + a.Label);
            var newRange = RangeNavigator.GetSelectedRange(oldSelection, oldcell, blockNavigationMode, navigationDirection, out a);
            var activeCell = SpreadsheetElement.GetWorkingActiveCell(sender);
            if (oldSelection is FullRowRange)
            {
                SpreadsheetElement.SetCurrentRangeForRoot(sender,newRange, activeCell);
                WorksheetPane pane1 = SpreadsheetElement.GetWorksheetPane((DependencyObject)sender);
                pane1.EnsureVisibleRow(a);
                return;
            }
            if (oldSelection is FullColumnRange)
            {
                SpreadsheetElement.SetCurrentRangeForRoot(sender, newRange, activeCell);
                WorksheetPane pane1 = SpreadsheetElement.GetWorksheetPane((DependencyObject)sender);
                pane1.EnsureVisibleColumn(a);

                return;
            }
            SpreadsheetElement.SetCurrentRangeForRoot(sender, newRange, oldcell);
            //ws.Selection = selection;
            WorksheetPane pane = SpreadsheetElement.GetWorksheetPane((DependencyObject) sender);
            pane.EnsureVisible(a);
        }

        private static void enableCommand(UIElement grid, ICommand command, Key key, ModifierKeys modifier,
                                          ExecutedRoutedEventHandler del, CanExecuteRoutedEventHandler del2)
        {
            grid.CommandBindings.Add(new CommandBinding(command, del, del2));
            grid.InputBindings.Add(new KeyBinding(command, key, modifier));
        }

        static ExecutedRoutedEventHandler moveHandler(UIElement grid, NavigationDirection direction)
        {
            return delegate(object sender, ExecutedRoutedEventArgs args)
                       {
                           EditedCell c =
                               EditionElement.GetEditedCell((DependencyObject) sender);
                           if (c != null)
                           {
                               EditedCellResources.StopEditing(CommitType.Formula, c);
                               EditionElement.SetEditedCellFor(grid, null);
                           }
                           moveActive(sender, BlockNavigationMode.ByCell, direction);
                           args.Handled = true;
                       };

        }

        static ExecutedRoutedEventHandler moveBlockHandler (object grid, NavigationDirection direction)
        {
            return delegate(object sender, ExecutedRoutedEventArgs args)
                {
                    EditedCell c = EditionElement.GetEditedCell((DependencyObject) sender);
                    if (c != null)
                    {
                        EditedCellResources.StopEditing(CommitType.Formula, c);
                        EditionElement.SetEditedCellFor(grid, null);
                    }
                    moveActive(sender, BlockNavigationMode.ByBlock, direction);
                    args.Handled = true;
                };
        }
        internal static void EnableCommands(UIElement grid)
        {
            var g = grid;


            enableCommand(grid, EditingCommands.MoveDownByLine, Key.Down, ModifierKeys.None, moveHandler(grid, NavigationDirection.Down)
                        , onCanMoveDown);
            enableCommand(grid, SpreadsheetEditingCommands.ToggleEndMode, Key.End, ModifierKeys.None,
                          delegate(object sender, ExecutedRoutedEventArgs args)
                              {
                                  bool isEndMode = EditionElement.GetIsInEndMode((DependencyObject) sender);
                                  EditionElement.SetIsEndModeFor((DependencyObject) sender, !isEndMode);
                                  args.Handled = true;
                              }, onAlways);

            enableCommand(grid, SpreadsheetEditingCommands.MoveDownByBlock, Key.Down, ModifierKeys.Control,
                moveBlockHandler(grid, NavigationDirection.Down)
                , onCanMoveDown);

            enableCommand(grid, EditingCommands.MoveUpByLine, Key.Up, ModifierKeys.None,
                          moveHandler(grid, NavigationDirection.Up),
                          onCanMoveUp);

            enableCommand(grid, SpreadsheetEditingCommands.MoveUpByBlock, Key.Up, ModifierKeys.Control,
                          moveBlockHandler(grid, NavigationDirection.Up)
                ,
                          onCanMoveUp);

            enableCommand(grid, EditingCommands.MoveLeftByWord, Key.Left, ModifierKeys.None,
                          moveHandler (grid, NavigationDirection.Left),
                          onCanMoveDown);
            enableCommand(grid, SpreadsheetEditingCommands.MoveLeftByBlock, Key.Left, ModifierKeys.Control,
                          moveBlockHandler(grid, NavigationDirection.Left),
                
                          onCanMoveDown);
            enableCommand(grid, EditingCommands.MoveRightByWord, Key.Right, ModifierKeys.None,
                          moveHandler(grid, NavigationDirection.Right),
                          onCanMoveDown);
            enableCommand(grid, SpreadsheetEditingCommands.MoveRightByBlock, Key.Right, ModifierKeys.Control,
                          moveBlockHandler(grid, NavigationDirection.Right),
                          onCanMoveDown);
            addCommandAndInputBinding(grid, new RoutedUICommand(),
                                      delegate(object sender, ExecutedRoutedEventArgs args)
                                          {
                                              Worksheet ws = getWorksheet(sender);
                                              if (ws.Selection != null && ws.Selection.Surface != 1)
                                              {
                                                  Cell cell = ws.ActiveCell ?? ws.Selection.FirstCell;
                                                  ws.ActiveCell = ws.Selection.GetNextCell(cell, CellNavigationDirection.Forward);
                                              }
                                              else
                                              {
                                                  moveActive(sender, BlockNavigationMode.ByCell,
                                                             NavigationDirection.Right);
                                              }
                                          },
                                      onCanMoveDown, new KeyGesture(Key.Tab));
            addCommandAndInputBinding(grid, new RoutedUICommand(),
                                      delegate(object sender, ExecutedRoutedEventArgs args)
                                          {
                                              Worksheet ws = getWorksheet(sender);
                                              if (ws.Selection != null && ws.Selection.Surface != 1)
                                              {
                                                  ws.ActiveCell = ws.Selection.GetNextCell(ws.ActiveCell, CellNavigationDirection.Backward);
                                              }
                                              else
                                              {
                                                  moveActive(sender, BlockNavigationMode.ByCell,
                                                             NavigationDirection.Left);
                                              }
                                          },
                                      onCanMoveDown, new KeyGesture(Key.Tab, ModifierKeys.Shift));

            addCommandAndInputBinding(grid, EditingCommands.MoveDownByPage,
                                      delegate(object sender, ExecutedRoutedEventArgs args)
                                          {
                                              GridEditing.StopEditing(sender, false);
                                              Worksheet ws = getWorksheet(sender);
                                              var e = (FrameworkElement) sender;
                                              double h = e.ActualHeight;
                                              if (ws.ActiveCell == null)
                                              {
                                                  ws.ActiveCell = ws[new CellAddress(0, 0)];
                                              }
                                              Cell c = ws.ActiveCell;
                                              double h0 = ws.Rows[ws.ActiveCell.Row.Index].Offset;
                                              while (ws.Rows[c.Row.Index].Offset - h0 < h)
                                                  c = c.GetNeighbor(NavigationDirection.Down);
                                              c.Select();
                                              if (sender is WorksheetGrid)
                                              {
                                                  ((WorksheetGrid) sender).MakeVisible(ws.ActiveCell.Address);
                                              }
                                          },
                                      onCanMoveDown, new KeyGesture(Key.PageDown));

            addCommandAndInputBinding(grid, EditingCommands.SelectDownByPage,
                                      delegate(object sender, ExecutedRoutedEventArgs args)
                                      {
                                          GridEditing.StopEditing(sender, false);
                                          Worksheet ws = getWorksheet(sender);
                                          var e = (FrameworkElement)sender;
                                          double h = e.ActualHeight;
                                          if (ws.ActiveCell == null)
                                          {
                                              ws.ActiveCell = ws[new CellAddress(0, 0)];
                                          }
                                          var oldCell = ws.ActiveCell.Address;
                                          Cell c =  ws[RangeNavigator.GetOpposite(ws.Selection, oldCell)];
                                          double h0 = ws.Rows[c.Row.Index].Offset;
                                          while (ws.Rows[c.Row.Index].Offset - h0 < h)
                                              c = c.GetNeighbor(NavigationDirection.Down);
                                        //  c.Select();
                                          ws.Selection = CellsRange.Create(ws, new RangeAddress(oldCell, c.Address));
                                          if (sender is WorksheetGrid)
                                          {
                                              ((WorksheetGrid)sender).MakeVisible(c.Address);
                                          }
                                      },
                                      onCanMoveDown, new KeyGesture(Key.PageDown, ModifierKeys.Shift));

            addCommandAndInputBinding(grid, EditingCommands.MoveUpByPage,
                                      delegate(object sender, ExecutedRoutedEventArgs args)
                                          {
                                              GridEditing.StopEditing(sender, false);
                                              Worksheet ws = getWorksheet(sender);
                                              var e = (FrameworkElement) sender;
                                              double h = e.ActualHeight;
                                              if (ws.ActiveCell == null)
                                              {
                                                  ws.ActiveCell = ws[new CellAddress(0, 0)];
                                              }
                                              Cell c = ws.ActiveCell;
                                              double h0 = ws.Rows[c.Row.Index].Offset;
                                              while (h0 - ws.Rows[c.Row.Index].Offset < h)
                                              {
                                                  if (c.Row.Index == 0)
                                                      break;
                                                  c = c.GetNeighbor(NavigationDirection.Up);
                                              }
                                              Worksheet.SetActiveCell(c);
                                              if (sender is WorksheetGrid)
                                              {
                                                  ((WorksheetGrid) sender).MakeVisible(ws.ActiveCell.Address);
                                              }
                                          },
                                      onCanMoveUp, new KeyGesture(Key.PageUp)); //\\TODO ctrl navigation


            addCommandAndInputBinding(grid, EditingCommands.SelectUpByPage,
                                      delegate(object sender, ExecutedRoutedEventArgs args)
                                      {
                                          GridEditing.StopEditing(sender, false);
                                          Worksheet ws = getWorksheet(sender);
                                          var e = (FrameworkElement)sender;
                                          double h = e.ActualHeight;
                                          if (ws.ActiveCell == null)
                                          {
                                              ws.ActiveCell = ws[new CellAddress(0, 0)];
                                          }
                                          var oldCell = ws.ActiveCell.Address;
                                          Cell c = ws[RangeNavigator.GetOpposite(ws.Selection, oldCell)];
                                          double h0 = ws.Rows[c.Row.Index].Offset;
                                          while (h0 - ws.Rows[c.Row.Index].Offset < h)
                                          {
                                              if (c.Row.Index == 0)
                                                  break;
                                              c = c.GetNeighbor(NavigationDirection.Up);
                                          }
                                          ws.Selection = CellsRange.Create(ws, new RangeAddress(c.Address, oldCell));
                                          if (sender is WorksheetGrid)
                                          {
                                              ((WorksheetGrid)sender).MakeVisible(c.Address);
                                          }
                                      },
                                      onCanMoveUp, new KeyGesture(Key.PageUp, ModifierKeys.Shift)); //\\TODO ctrl navigation
            addCommandAndInputBinding(grid, EditingCommands.MoveToLineStart,
                                      delegate(object sender, ExecutedRoutedEventArgs args)
                                          {
                                              GridEditing.StopEditing(sender, false);
                                              Worksheet ws = getWorksheet(sender);
                                              DependencyObject e = (FrameworkElement) sender;
                                              if (ws.ActiveCell == null)
                                              {
                                                  ws.ActiveCell = ws[new CellAddress(0, 0)];
                                              }
                                              Cell c = ws[ws.ActiveCell.Row.Index, 0];
                                              Worksheet.SetActiveCell(c);
                                              if (sender is WorksheetGrid)
                                              {
                                                  ((WorksheetGrid) sender).MakeVisible(ws.ActiveCell.Address);
                                              }
                                          }, onCanMoveDown, new KeyGesture(Key.Home));


            addCommandAndInputBinding(grid, EditingCommands.MoveToDocumentStart,
                                      delegate(object sender, ExecutedRoutedEventArgs args)
                                          {
                                              GridEditing.StopEditing(sender, false);
                                              Worksheet ws = getWorksheet(sender);
                                              Worksheet.SetActiveCell(ws[new CellAddress()]);
                                              if (sender is WorksheetGrid)
                                              {
                                                  ((WorksheetGrid) sender).MakeVisible(ws.ActiveCell.Address);
                                              }
                                          }, onCanMoveDown, new KeyGesture(Key.Home, ModifierKeys.Control));

            addCommandAndInputBinding(grid, EditingCommands.MoveToDocumentEnd,
                                      delegate(object sender, ExecutedRoutedEventArgs args)
                                      {
                                          GridEditing.StopEditing(sender, false);
                                          Worksheet ws = getWorksheet(sender);
                                          var ur = ws.UsedRange;
                                          var ca = new CellAddress(ur.RowSpan - 1 + ur.Row, ur.ColumnSpan - 1 + ur.Column);
                                          Worksheet.SetActiveCell(ws[ca]);
                                          if (sender is WorksheetGrid)
                                          {
                                              ((WorksheetGrid)sender).MakeVisible(ws.ActiveCell.Address);
                                          }
                                      }, onCanMoveDown, new KeyGesture(Key.End, ModifierKeys.Control));
            
            addCommandAndInputBinding(grid, EditingCommands.SelectDownByLine,
                                      delegate(object sender, ExecutedRoutedEventArgs args) { select(sender, BlockNavigationMode.ByCell, NavigationDirection.Down); },
                                      onCanMoveDown, new KeyGesture(Key.Down, ModifierKeys.Shift));


            addCommandAndInputBinding(grid, EditingCommands.SelectUpByLine,
                                      delegate(object sender, ExecutedRoutedEventArgs args) { select(sender, BlockNavigationMode.ByCell, NavigationDirection.Up); },
                                      onCanMoveUp, new KeyGesture(Key.Up, ModifierKeys.Shift));

            addCommandAndInputBinding(grid, EditingCommands.SelectLeftByWord,
                                      delegate(object sender, ExecutedRoutedEventArgs args) { select(sender, BlockNavigationMode.ByCell, NavigationDirection.Left); },
                                      onCanMoveDown, new KeyGesture(Key.Left, ModifierKeys.Shift));
            addCommandAndInputBinding(grid, EditingCommands.SelectRightByWord,
                                      delegate(object sender, ExecutedRoutedEventArgs args) { select(sender, BlockNavigationMode.ByCell, NavigationDirection.Right); },
                                      onCanMoveDown, new KeyGesture(Key.Right, ModifierKeys.Shift));



            addCommandAndInputBinding(grid, SpreadsheetEditingCommands.SelectDownByBlock,
                          delegate(object sender, ExecutedRoutedEventArgs args) { select(sender, BlockNavigationMode.ByBlock, NavigationDirection.Down); },
                          onCanMoveDown, new KeyGesture(Key.Down, ModifierKeys.Shift | ModifierKeys.Control));


            addCommandAndInputBinding(grid, SpreadsheetEditingCommands.SelectUpByBlock,
                                      delegate(object sender, ExecutedRoutedEventArgs args) { select(sender, BlockNavigationMode.ByBlock, NavigationDirection.Up); },
                                      onCanMoveUp, new KeyGesture(Key.Up, ModifierKeys.Shift | ModifierKeys.Control));

            addCommandAndInputBinding(grid, SpreadsheetEditingCommands.SelectLeftByBlock,
                                      delegate(object sender, ExecutedRoutedEventArgs args) { select(sender, BlockNavigationMode.ByBlock, NavigationDirection.Left); },
                                      onCanMoveDown, new KeyGesture(Key.Left, ModifierKeys.Shift | ModifierKeys.Control));
            addCommandAndInputBinding(grid, SpreadsheetEditingCommands.SelectRightByBlock,
                                      delegate(object sender, ExecutedRoutedEventArgs args) { select(sender, BlockNavigationMode.ByBlock, NavigationDirection.Right); },
                                      onCanMoveDown, new KeyGesture(Key.Right, ModifierKeys.Shift | ModifierKeys.Control));

            addCommandAndInputBinding(grid, SpreadsheetCommands.Enter,
                                      delegate(object sender, ExecutedRoutedEventArgs args)
                                          {
                                              //RichGrid g = (RichGrid)sender;
                                              //g.StopEditing(true);
                                              EditingCommands.MoveDownByLine.Execute(null, ((IInputElement) sender));
                                          },
                                      onCanMoveDown, new KeyGesture(Key.Enter));
           /* addCommandAndInputBinding(grid, SpreadsheetCommands.NextSheet,
                          delegate(object sender, ExecutedRoutedEventArgs args)
                          {
                              //RichGrid g = (RichGrid)sender;
                              //g.StopEditing(true);
                              EditingCommands.MoveDownByLine.Execute(null, ((IInputElement)sender));
                          },
                          onCanMoveDown, new KeyGesture(Key.PageDown, ModifierKeys.Control)); 
            addCommandAndInputBinding(grid, SpreadsheetCommands.PreviousSheet,
                           delegate(object sender, ExecutedRoutedEventArgs args)
                           {
                               //RichGrid g = (RichGrid)sender;
                               //g.StopEditing(true);
                               EditingCommands.MoveDownByLine.Execute(null, ((IInputElement)sender));
                           },
                          onCanMoveDown, new KeyGesture(Key.PageUp, ModifierKeys.Control));*/
            //\\todo            EventManager.RegisterClassHandler(typeof(Grid), UIElement.PreviewKeyDownEvent, new KeyEventHandler(OnKeyDown));
        }

        private static void addCommandAndInputBinding(UIElement grid, ICommand command,
                                                      ExecutedRoutedEventHandler onExecute,
                                                      CanExecuteRoutedEventHandler onCanExecute, KeyGesture g)
        {
            grid.CommandBindings.Add(new CommandBinding(command,
                                                        onExecute,
                                                        onCanExecute));
            grid.InputBindings.Add(new InputBinding(command, g));
        }
    }
}